/**
 *
 * Licensed Property to China UnionPay Co., Ltd.
 * 
 * (C) Copyright of China UnionPay Co., Ltd. 2010
 *     All Rights Reserved.
 *
 * 
 * Modification History:
 * =============================================================================
 *   Author         Date          Description
 *   ------------ ---------- ---------------------------------------------------
 *   anonymous       2014-05-28      MPI工具类
 * =============================================================================
 */
package com.unionpay.fsas.sdk.gm;

import static com.unionpay.fsas.sdk.SDKUtil.isEmpty;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import com.unionpay.fsas.sdk.CertUtil;
import com.unionpay.fsas.sdk.SDKConfig;

/**
 * @ClassName: GmCertUtil
 * @Description: fsassdk证书工具类，主要用于对证书的加载和使用
 * 
 * 声明：以下代码只是为了方便机构测试而提供的样例代码，机构可以根据自己需要，按照技术文档编写。
 *  该代码仅供参考，不提供编码，性能，规范性等方面的保障<br>
 */
public class GmCertUtil{

    private final static Logger logger = Logger.getLogger(GmCertUtil.class);
	
	private static ConcurrentHashMap<String, Key> verifyCertsSnMap = new ConcurrentHashMap<String, Key>();
	private static ConcurrentHashMap<String, Key> signCertsPathMap = new ConcurrentHashMap<String, Key>();
    private static ConcurrentHashMap<String, Key> signCertsSnMap = new ConcurrentHashMap<String, Key>();
	private static X509Certificate middleCert = null;
	private static X509Certificate rootCert = null;
	
	static{
		addProvider();
		init();
	}
	
	/**
	 * 添加签名，验签，加密算法提供者
	 */
	private static void addProvider(){
		if (Security.getProvider("BC") == null) {
			logger.info("add BC provider");
			Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
		} else {
			Security.removeProvider("BC"); //解决eclipse调试时tomcat自动重新加载时，BC存在不明原因异常的问题。
			Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
			logger.info("re-add BC provider");
		}
	}
	
    /**
     * 如果需要改用其他properties文件，可以调这个，默认用Config类默认使用调文件。
     * @param config
     */
	public static void init(){
		verifyCertsSnMap.clear();
		signCertsPathMap.clear();
	    signCertsSnMap.clear();
		getSignKey();
		initVerifySignCerts();
		initMiddleCert();//初始化验签证书的中级证书
		initRootCert();//初始化验签证书的根证书
	}

	/**
	 * 用配置文件fsas_sdk.properties配置路径 加载5.1验签证书中级证书
	 */
	private static void initMiddleCert() {
        String path = SDKConfig.getConfig().getSm2MiddleCertPath();
        if(isEmpty(path)){
            logger.warn(SDKConfig.SDK_SM2_MIDDLECERT_PATH + " is empty");
            return;
        }
        middleCert = CertUtil.readX509Cert(path);
        logger.info("加载中级证书==>" + path + (middleCert != null ?"成功":"失败"));
	}

	/**
     * 用配置文件fsas_sdk.properties配置路径 加载5.1验签证书根证书
	 */
	private static void initRootCert() {
        String path = SDKConfig.getConfig().getSm2RootCertPath();
        if(isEmpty(path)){
            logger.warn(SDKConfig.SDK_SM2_ROOTCERT_PATH + " is empty");
            return;
        }
        rootCert = CertUtil.readX509Cert(path);
        logger.info("加载根证书==>" + path + (rootCert != null ?"成功":"失败"));
	}
	
	private static void initVerifySignCerts(){
		String dir = SDKConfig.getConfig().getValidateCertDir();
		ConcurrentHashMap<String, Key> tmpVerifyCerts = new ConcurrentHashMap<String, Key>();
		logger.info("加载验证签名证书目录==>" + dir);
		if (dir == null || dir.trim().length() == 0) {
			logger.error("WARN, empty path:" + dir);
			return;
		}
		CertificateFactory cf = null;
		FileInputStream in = null;
		try {
			cf = CertificateFactory.getInstance("X.509", "BC");
		} catch (Exception e) {
			logger.error("LoadVerifyCert Error", e);
			return ;
		}
		File fileDir = new File(dir);
		File[] files = fileDir.listFiles(new CerFilter());
		for (int i = 0; i < files.length; i++) {
			File file = files[i];
			try {
				in = new FileInputStream(file.getAbsolutePath());
				X509Certificate cert = (X509Certificate) cf.generateCertificate(in);
				if(cert == null) {
					logger.error("Load verify cert error, " + file.getAbsolutePath() + " has error cert content.");
					continue;
				}
				Key key = new Key();
				key.setCertId(cert.getSerialNumber().toString(10));
				key.setPubKey(cert.getPublicKey());
				tmpVerifyCerts.put(key.getCertId(), key);
				// 打印证书加载信息,供测试阶段调试
				logger.info("[" + file.getAbsolutePath() + "][CertId=" + key.getCertId() +"]");
			} catch (CertificateException e) {
				logger.error("LoadVerifyCert Error CertificateException", e);
			}catch (FileNotFoundException e) {
				logger.error("LoadVerifyCert Error File Not Found", e);
			}catch (Exception e) {
				logger.error("LoadVerifyCert Error", e);
			}finally {
				if (null != in) {
					try {
						in.close();
					} catch (IOException e) {
						logger.error(e.toString());
					}
				}
			}
		}
		logger.info("LoadVerifyCert Finish");
		if(tmpVerifyCerts != null && tmpVerifyCerts.size() > 0){
			verifyCertsSnMap = tmpVerifyCerts;
		}
	}
	
	private static Key getVerifySignKey(String certId){
		if(certId == null)
			throw new IllegalArgumentException("null argument");
		if(!verifyCertsSnMap.containsKey(certId))
			initVerifySignCerts();
		if(verifyCertsSnMap.containsKey(certId))
			return verifyCertsSnMap.get(certId);
		logger.error("cannot find this cert: " + certId);
		return null;
	}

	public static PublicKey getValidatePublicKey(String certId){
		return getVerifySignKey(certId).getPubKey();
	}

	/**
	 * 
	 * @param certFilePath
	 * @param certPwd
	 */
	private static Key getSignKeyByPath(String certFilePath, String certPwd) {
		if(certFilePath == null || certPwd == null)
			throw new IllegalArgumentException("null argument");
		if(!signCertsPathMap.containsKey(certFilePath)){
			Key key = readGmPriCert(certFilePath, certPwd);
			if(key != null) {
				logger.info("read sign key from [" + certFilePath + "] succeed [" + key.getCertId() + "]");
                signCertsPathMap.put(certFilePath, key);
                signCertsSnMap.put(key.getCertId(), key);
            }
		}
		return signCertsPathMap.get(certFilePath);
	}

    /**
     *
     * @return
     */
    private static Key getSignKey() {
		String path = SDKConfig.getConfig().getSm2SignCertPath();
		String pwd = SDKConfig.getConfig().getSm2SignCertPwd();
        if(path == null || pwd == null) {
        	logger.error("没有找到默认签名证书默认配置。");
            return null;
        }
        return getSignKeyByPath(path, pwd);
    }
    
    public static String getSignCertId() {
		return getSignKey().getCertId();
	}
    
    public static PrivateKey getSignCertPrivateKey() {
		return getSignKey().getPriKey();
	}
    
    public static String getCertIdByKeyStoreMap(String path, String pwd) {
		return getSignKeyByPath(path, pwd).getCertId();
	}
    
    public static PrivateKey getSignCertPrivateKey(String path, String pwd) {
		return getSignKeyByPath(path, pwd).getPriKey();
	}

    private static Key readGmPriCert(String pfxkeyfile, String pwd) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(pfxkeyfile);
            byte[] bs = IOUtils.toByteArray(fis);
            bs = Base64.decodeBase64(bs);
            GmUtil.Sm2Cert cert = GmUtil.readSm2File(bs, pwd);
            Key key = new Key();
            key.setCertId(new BigInteger(cert.getCertId(), 10).toString(10));
            key.setPriKey(cert.getPrivateKey());
            return key;
        } catch (Exception e) {
            logger.error("getKeyInfo Error [" + pfxkeyfile + "]", e);
            return null;
        } finally {
            if(null != fis)
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }

    
    static class Key {

        private String certId;
        private PublicKey pubKey;
        private PrivateKey priKey;

        public String getCertId() {
            return certId;
        }

        public PublicKey getPubKey() {
            return pubKey;
        }

        public PrivateKey getPriKey() {
            return priKey;
        }

        public void setCertId(String certId) {
            this.certId = certId;
        }

        public void setPubKey(PublicKey pubKey) {
            this.pubKey = pubKey;
        }

        public void setPriKey(PrivateKey priKey) {
            this.priKey = priKey;
        }

        @Override
        public String toString() {
            return "key: certId="  + certId;
        }
    }
    
    /**
     * 证书文件过滤器
     * 
     */
    static class CerFilter implements FilenameFilter {
        public boolean isCer(String name) {
            if (name.toLowerCase().endsWith(".cer")) {
                return true;
            } else {
                return false;
            }
        }
        public boolean accept(File dir, String name) {
            return isCer(name);
        }
    }    
}
